﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Remoting.Channels;
using System.Net.Sockets;
using System.Runtime.Serialization.Formatters;
using System.Collections;
using System.Threading;
using System.Runtime.Remoting;

namespace commonlib
{
    [Serializable]
    public class Client : SystemComponent
    {
        CentralDirectoryServices directoryServices;
        DataServerServices dataServerServices;
        ClientServices clientServices;
        
        public Client(long id, int port)
        {
            this.id = id;
            this.port = port;
            this.ipAddress = Array.FindLast(
                    Dns.GetHostEntry(string.Empty).AddressList,
                    a => a.AddressFamily == AddressFamily.InterNetwork);
        }

        
        public Client(string name, string ipaddress, int port)
        {
            this.name = name;
            this.port = port;
            this.ipAddress = IPAddress.Parse(ipaddress);
        }
       
        /**
         * Este objecto mantém o registo da transação corrente
         **/
        private Transaction transaction
        { get; set; }

        public void initServices()
        {     
            /*
            this.ipAddress = Array.FindLast(
                    Dns.GetHostEntry(string.Empty).AddressList,
                    a => a.AddressFamily == AddressFamily.InterNetwork);
            */
            Console.WriteLine("<Data Server> ipAddress:{0} port:{1}", this.ipAddress, this.port);

            BinaryServerFormatterSinkProvider provider = new BinaryServerFormatterSinkProvider();
            provider.TypeFilterLevel = TypeFilterLevel.Full;
            IDictionary props = new Hashtable();
            props["port"] = 8088;
            props["name"] = "clientchannel";
            TcpChannel channel = new TcpChannel(props, null, provider);
            ChannelServices.RegisterChannel(channel, true);

            RemotingConfiguration.RegisterWellKnownServiceType(
                                        typeof(ClientServices),
                                         "ClientServices",
                                            WellKnownObjectMode.Singleton);

            Console.WriteLine("<Client Services> serviços inicializados.");

        }

        /**
         * Este método solicita o registo ao central directory
         **/
        public void requestRegister()
        {
            /*
                    this.ipAddress = Array.FindLast(
                    Dns.GetHostEntry(string.Empty).AddressList,
                    a => a.AddressFamily == AddressFamily.InterNetwork);
            */
            BinaryServerFormatterSinkProvider provider = new BinaryServerFormatterSinkProvider();
            provider.TypeFilterLevel = TypeFilterLevel.Full;
            IDictionary props = new Hashtable();
            props["port"] = this.port;
            props["name"] = "centraldirectorychannel";

            TcpChannel channel = new TcpChannel(props, null, provider);
            ChannelServices.RegisterChannel(channel, true);
          
            directoryServices = (CentralDirectoryServices)Activator.GetObject(
              typeof(CentralDirectoryServices),
              "tcp://"+this.centralAdress+":"+this.centralPort+"/CentralDirectoryServices");

            try
            {
                Console.WriteLine("<{0}> request register...", this.name);
                this.id = directoryServices.registerComponent(this);
                Console.WriteLine("<{0}> registado com id {1}",this.name,this.id);
            }
            catch (SocketException ex)
            {
                throw new PaditableException("Não foi possivel contactar o Central Directory. \n" + ex.Message);
            }

        }

        /**
         *  Connectar a um dataserver especifico
         **/
        public void connectToDataServer(IPAddress address, int port)
        {
            try
            {
                dataServerServices = (DataServerServices)Activator.GetObject(
                typeof(DataServerServices),
                "tcp://" + address + ":" + port + "/DataServerServices");
            }
            catch (SocketException ex)
            {
                throw new PaditableException("Nao foi possivel contactar o Data Server em: tcp://" + address + ":" + port + "/DataServerServices \n" + ex);
            }

        }

        /**
         * Este método solicita o txId ao central directory
         **/
        public long requestTransactionId()
        {
            directoryServices = (CentralDirectoryServices)Activator.GetObject(
                typeof(CentralDirectoryServices),
                "tcp://" + this.centralAdress + ":" + this.centralPort + "/CentralDirectoryServices");

            long newTxId = 0;
            try
            {
                newTxId = directoryServices.getTxId();
                Console.WriteLine("client requested new txid :" + newTxId);
               
            }
            catch (SocketException ex)
            {
                throw new PaditableException("Não foi possivel contactar o Central Directory. \n" + ex.Message);
            }

            return newTxId;
        }

        /**
         *  Solicita o estado do sistema
         **/
        public void requestSystemUpdate()
        {
            while (true)
            {
                try
                {
                    Console.WriteLine("<Client> request system state");
                    Thread.Sleep(this.requestUpdateSystemTimeOut);

                    directoryServices = (CentralDirectoryServices)Activator.GetObject(
                 typeof(CentralDirectoryServices),
                 "tcp://" + this.centralAdress + ":" + this.centralPort + "/CentralDirectoryServices");

                    this.registeredDataServers = directoryServices.getRegisteredDataServers();
                    this.registeredClients = directoryServices.getRegisteredClients();
                    Console.WriteLine("<Client> :\n " + this + " ");

                }
                catch (SocketException)
                {
                    System.Console.WriteLine("Nao foi possivel contactar o Central Directory.");
                }

            }
        }

        /**
         * Iniciar transaçao
         **/
        public void beginTx(string key,string value)
        {
            long newTxId = requestTransactionId();
            requestSystemUpdate();
            List<DataServer> dsInTransaction = new List<DataServer>();

            Random random = new Random();
            int index = PaditableUtil.RandomNumber(0, this.registeredDataServers.Count - 1);
            Console.WriteLine("random server :" + index+" newTxId:"+newTxId);

            dsInTransaction.Add(this.registeredDataServers[index]);
            this.transaction = new Transaction(key, value, newTxId, dsInTransaction);
            this.transaction.state = TRANSACTION_STATES.INITIATED;

            try
            {
                foreach (DataServer ds in dsInTransaction)
                {
                    connectToDataServer(ds.ipAddress, ds.port);
                    if (this.dataServerServices != null)
                    {
                        this.dataServerServices.begin(newTxId);
                    }
                }

            }
            catch (Exception pe)
            {
                Console.WriteLine(pe.Message);
            }

        }

        /**
         * Commit transação
         **/
        public void commitTx()
        {
        }

        /**
         * Este método é usado para solicitar o registo de dados em algum data server
         **/
        public void requestPut(string key, string value, long txId)
        {

        }

        /**
         * Este método é usado para solicitar a leitura de dados em algum data server
         **/
        public Tuple requestGet(string key, long txId)
        {
            Tuple storedPair = null;

            return storedPair;
        }

    }
}
